#include "ptwrap.hpp"
#include <deque>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>

// [[ex04dg]]
// DEBUG descr The modifier fails to prevent the race.
struct Message 
{
	virtual ~Message() { } // allow for polymorphism

	enum {
		EXIT,
		DUMMY,
		NORMAL // first normal type
	};
	int msgtype_;
	// ... whatever else is needed by the application ...
};

class Modifier;

class Queue
{
	friend class Modifier;
public:
	enum {
		DEFAULT_LIMIT = 1000
	};

	Queue(int limit = DEFAULT_LIMIT) :
		limit_(limit), 
		exitposted_(false),
		dqwr_(&deque1_), dqrd_(&deque2_), condrd_(condwr_)
	{ }

	virtual ~Queue()
	{ 
		Message *m;
		while (!deque1_.empty()) {
			m = deque1_.front();
			deque1_.pop_front();
			delete m;
		}
		while (!deque2_.empty()) {
			m = deque2_.front();
			deque2_.pop_front();
			delete m;
		}
	}

	// DEBUG getfrom pop_front
	// DEBUG pop_front body is moved to after Modifier
	Message *pop_front();
	// DEBUG endget

	// takes over the ownership of the message;
	// returns true if the message was posted, false
	// if discarded
	virtual bool post(Message *m)
	{
		pw::lockmutex lm(mutexx_);
		if (exitposted_) {
			delete m;
			return false;
		}
		if (m->msgtype_ == Message::EXIT)
			exitposted_ = true;
		push_back(m);
		return true;
	}


protected:
	void push_back(Message *m)
	{
		pw::lockmutex lm(condwr_);

		while (dqwr_->size() >= limit_)
			// it releases and gets back the mutex
			condwr_.wait(); 

		dqwr_->push_back(m);
		condrd_.broadcast();
	}

	void add_modifier(Modifier *mod)
	{
		pw::lockmutex lm(mutexmod_);
		mods_.push_back(mod);
	}

	void push_front(Message *m)
	{
		dqwr_->push_front(m);
	}

	int limit_;
	bool exitposted_;
	std::deque<Message *> deque1_, deque2_;
	pw::pmutex mutexrd_;
	std::deque<Message *> *dqwr_, *dqrd_;
	pw::pmcond condwr_;
	pw::pchaincond condrd_;
	pw::pmutex mutexx_;

	pw::pmutex mutexmod_;
	std::deque<Modifier *> mods_;
	pw::pmcond moddone_;
};

class Modifier
{
	friend class Queue;
public:
	Modifier(Queue *q) : q_(q) 
	{
		// ...
	}

	void execute() 
	{
		// ...
		// now it's time to do the modification
		q_->add_modifier(this);
		Message *m = new Message;
		m->msgtype_ = Message::DUMMY;
		{
			pw::lockmutex lm(ready_);
			q_->post(m);
			ready_.wait();
		}
		// ... calculate the modification, build the
		// message in m....
		q_->push_front(m);
		q_->moddone_.signallock();

		// ...
	}

protected:
	pw::pmcond ready_;
	Queue *q_;
};
// DEBUG move pop_front
	// this combines the STL front() and pop_front()
	// since the popping must be atomic; the receiver
	// takes over the ownership of the message
	Message */*DEBUG{*/Queue::/*}DEBUG*/pop_front()
	{
		pw::lockmutex lm(mutexrd_);

		while(dqrd_->empty()) {
			// CANCEL is not supported, so no need to do
			// tricks for unlocking mutexrd_ when sleeping
			pw::lockmutex lm(condwr_);

			{
				pw::lockmutex lmm(mutexmod_);
				while(!mods_.empty()) {
					Modifier *mod = mods_.front();
					mods_.pop_front();

					pw::lockmutex lm(moddone_);
					mod->ready_.signallock();
					moddone_.wait();
				}
			}

			if (dqwr_->empty()) {
				// it releases and gets back the mutex
				condrd_.wait(); 
			} else {
				swap(dqwr_, dqrd_);
				// if the write buffer was full, wake up the
				// writers
				condwr_.broadcast();
			}
		}

		Message *m = dqrd_->front();
		dqrd_->pop_front();
		return m;
	}
// DEBUG endmove pop_front
// END
